package com.example.util.util;

import com.example.util.model.Response;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpVersion;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.client.*;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.util.StringUtils;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.concurrent.TimeUnit;

@Slf4j
public class HttpTemplate {


    /**
     * 最大连接数
     */
    private static final int MAX_CONNECTION_NUM = 80;

    /**
     * 单路由最大连接数
     */
    private static final int MAX_PER_ROUTE = 80;

    /**
     * 连接超时时间，缺省为3秒钟
     */
    private static final int DEFAULT_CONNECTION_TIMEOUT = 3000;

    /**
     * 读取超时时间,缺省为3秒钟
     */
    private static final int DEFAULT_READ_TIMEOUT = 8000;

    /**
     * 连接池管理对象
     */
    private PoolingHttpClientConnectionManager poolingConnectionManager;

    private CloseableHttpClient httpClient;

    private static String chatbotKey = "C21021000003";

    private static String chatbotToken = "63f564d57b3df1ab4eb27bb1011b828bc3a6d6846d6cf37914f46c95dc6ba816";

    private static class SingletonHolder {
        private static final HttpTemplate INSTANCE = new HttpTemplate();
    }

    public static HttpTemplate initSingleton() {
        return SingletonHolder.INSTANCE;
    }

    private HttpTemplate() {
        initHttpClient();
    }

    private void initHttpClient() {

        HttpClientBuilder builder = HttpClients.custom();
        builder.disableCookieManagement();
        builder.disableRedirectHandling();
        builder.disableAutomaticRetries();

        builder.setConnectionManagerShared(true)
                .evictIdleConnections(1, TimeUnit.MINUTES)// 定期回收空闲连接
                .evictExpiredConnections()// 定期回收过期连接
                .setConnectionTimeToLive(1, TimeUnit.MINUTES) // 连接存活时间，如果不设置，则根据长连接信息决定
                .setDefaultRequestConfig(
                        RequestConfig
                                .custom()
                                .setAuthenticationEnabled(false)
                                .setCircularRedirectsAllowed(false)
                                .setSocketTimeout(DEFAULT_READ_TIMEOUT)
                                .setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT)
                                .setConnectionRequestTimeout(1000)
                                .setCookieSpec(CookieSpecs.IGNORE_COOKIES)
                                .build()) // 设置默认请求配置
                .setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE) // 连接重用策略 是否能keepAlive
                .setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE)// 长连接配置，即获取长连接生产多长时间
                .setRetryHandler(new DefaultHttpRequestRetryHandler(3, true))// 重试次数 默认3次 此处禁用
                .build();

        Registry socketFactoryRegistry =
                RegistryBuilder.create()
                        .register("https", createSSLConnSocketFactory())
                        .register("http", new PlainConnectionSocketFactory()).build();

        final int conExpire = 15;// 长连接闲置过期时间,可配置
        poolingConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, conExpire,
                TimeUnit.SECONDS);
        poolingConnectionManager.setDefaultConnectionConfig(ConnectionConfig.DEFAULT);
        poolingConnectionManager.setMaxTotal(MAX_CONNECTION_NUM);
        poolingConnectionManager.setDefaultMaxPerRoute(MAX_PER_ROUTE);
        // 设置长连接心跳检测，设置超时，禁用nagle算法
        poolingConnectionManager.setDefaultSocketConfig(SocketConfig.custom().setSoKeepAlive(true).setSoTimeout(DEFAULT_READ_TIMEOUT).setTcpNoDelay(true).build());
        poolingConnectionManager.setValidateAfterInactivity(-1);// 每次取重用的连接时禁止连接活性检测，可以提升性能

        builder.setConnectionManager(poolingConnectionManager);
        builder.setConnectionManagerShared(false);

        httpClient = builder.build();

        // 过期检测
        Thread staleCheckThread = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(10000);
                    poolingConnectionManager.closeExpiredConnections();
                    poolingConnectionManager.closeIdleConnections(conExpire, TimeUnit.SECONDS);
                } catch (Exception e) {
                    log.error("stale check exception", e);
                }
            }

        }, "HttpInvoker-coonection-stale-check-thread");
        // 设置成为守护线程
        staleCheckThread.setDaemon(true);
        staleCheckThread.start();
    }

    private void initHttpClient2() {
        clientHttpRequestFactory();
    }


    /**
     * 客户端请求链接策略
     *
     * @return
     */
    private ClientHttpRequestFactory clientHttpRequestFactory() {
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        httpClient = httpClientBuilder().build();
        clientHttpRequestFactory.setHttpClient(httpClient);
        clientHttpRequestFactory.setConnectTimeout(60000);  // 连接超时时间/毫秒
        clientHttpRequestFactory.setReadTimeout(60000); // 读写超时时间/毫秒
        clientHttpRequestFactory.setConnectionRequestTimeout(5000);// 请求超时时间/毫秒
        return clientHttpRequestFactory;
    }

    /**
     * 设置HTTP连接管理器,连接池相关配置管理
     *
     * @return 客户端链接管理器
     */
    private HttpClientBuilder httpClientBuilder() {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        httpClientBuilder.setConnectionManager(poolingConnectionManager());
        ConnectionKeepAliveStrategy connectionKeepAliveStrategy = (httpResponse, httpContext) -> 20 * 1000;
        httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy);
        httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler());
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(10 * 1000)
                .setSocketTimeout(10 * 1000)
                .setConnectionRequestTimeout(10 * 1000)
                .build();
        httpClientBuilder.setDefaultRequestConfig(requestConfig);
        httpClientBuilder.setConnectionManagerShared(true);
        try {

            httpClientBuilder.setSSLSocketFactory(createSSLConnSocketFactory());
        } catch (Exception e) {
            log.error("创建HttpClient异常", e);
        }
        return httpClientBuilder;
    }

    private static SSLConnectionSocketFactory createSSLConnSocketFactory(){
        SSLContext sslContext = null;
        try {
            sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                @Override
                public boolean isTrusted(X509Certificate[] chain, String authType) {
                    return true;
                }
            }).build();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        }
        HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
        return new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
    }



    /**
     * 链接线程池管理,可以keep-alive不断开链接请求,这样速度会更快 MaxTotal 连接池最大连接数 DefaultMaxPerRoute
     * 每个主机的并发 ValidateAfterInactivity
     * 可用空闲连接过期时间,重用空闲连接时会先检查是否空闲时间超过这个时间，如果超过，释放socket重新建立
     *
     * @return
     */
    private HttpClientConnectionManager poolingConnectionManager() {
        poolingConnectionManager = new PoolingHttpClientConnectionManager();
        poolingConnectionManager.setMaxTotal(1000);
        poolingConnectionManager.setDefaultMaxPerRoute(5000);
        poolingConnectionManager.setValidateAfterInactivity(30000);
        poolingConnectionManager.closeIdleConnections(30, TimeUnit.SECONDS);
        poolingConnectionManager.closeExpiredConnections();
        return poolingConnectionManager;
    }


//    /**
//     * 暴露给业务层调用的方法
//     *
//     * @param path          请求url
//     * @param req           请求对象
//     * @param typeReference 响应对象
//     * @param
//     * @return
//     * @throws Exception
//     */
//    public <T> T execute(String path, Object req, TypeReference typeReference) throws Exception {
//        if (typeReference == null) {
//            typeReference = (TypeReference) new TypeReference() {
//            };
//        }
//
//        String reqBody = objectMapper.writeValueAsString(req);
//        log.info("reqBody:{}", reqBody);
//
//        HttpPost httpPost = new HttpPost(path);
//        httpPost.setProtocolVersion(HttpVersion.HTTP_1_1);
//        httpPost.setHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_KEEP_ALIVE);
//        httpPost.setConfig(RequestConfig.custom().setSocketTimeout(DEFAULT_READ_TIMEOUT)
//                .setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT).build());
//
//        httpPost.addHeader("Accept", DEFAULT_ACCEPTTYPE);
//        httpPost.addHeader("Content-Type", DEFAULT_ACCEPTTYPE);
//
//        HttpEntity httpEntity = new StringEntity(reqBody, DEFAULT_CHARSET);
//        httpPost.setEntity(httpEntity);
//
//        CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
//        String responseBody = EntityUtils.toString(httpResponse.getEntity(), DEFAULT_CHARSET);
//        log.info("resBody:{}", responseBody);
//        return (T) objectMapper.readValue(responseBody, typeReference);
//    }

    /**
     * 暴露给业务层调用的方法
     *
     * @param path    请求url
     * @param reqBody 请求对象
     * @param
     * @return
     * @throws Exception
     */
    public Response executeXml(String path, String reqBody,String token) {

        final String gmtDate = GetColumns.getGMTDate();
        Response response = new Response();
        String authorization = null;
        if (StringUtils.isEmpty(token)) {
            authorization = chatbotKey + ":" + SHA256.getSHA256(chatbotToken + gmtDate);
        } else {
            authorization = chatbotKey + ":" + SHA256.getSHA256(token + gmtDate);
        }
        authorization = "Basic " + Base64.getUrlEncoder().encodeToString(authorization.getBytes());
        HttpPost httpPost = new HttpPost(path);

        httpPost.setHeader("Authorization", authorization);
        httpPost.setHeader("date", gmtDate);
        if (log.isDebugEnabled()) {
            log.debug("reqBody:{}", reqBody);
        }

        httpPost.setProtocolVersion(HttpVersion.HTTP_1_1);
        httpPost.setConfig(RequestConfig.custom().setSocketTimeout(DEFAULT_READ_TIMEOUT)
                .setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT).build());

        StringEntity stringEntity = new StringEntity(reqBody, ContentType.APPLICATION_XML.withCharset("utf-8"));
        httpPost.setEntity(stringEntity);

        CloseableHttpResponse httpResponse = null;
        try {
            httpResponse = httpClient.execute(httpPost);
            response.setResponseCode(httpResponse.getStatusLine().getStatusCode());
            InputStream content = httpResponse.getEntity().getContent();
            String json = IOUtils.toString(content);
            response.setResponseBody(json);
            response.setRequestBody(reqBody);
            content.close();
        } catch (Exception e) {
            log.error("调用maap平台出现异常_" + e.getMessage(), e);
            response.setResponseCode(-1);
            response.setResponseBody(null);
            return response;
        } finally {
            if (null != httpResponse) {
                try {
                    httpResponse.close();
                } catch (IOException ie) {
                    log.error("关流异常_" + ie.getMessage(), ie);
                }
            }
            if (httpPost != null) {
                httpPost.releaseConnection();
            }
            //链接池使用的时候不能关闭链接，不然下次使用会抛异常 java.lang.IllegalStateException: Connection pool shut down
            /*if (null != httpClient) {
                try {
                    httpClient.close();
                } catch (IOException ie) {
                    log.error("关流异常_" + ie.getMessage(), ie);
                }
            }*/
        }

        return response;
    }

}
 
 